﻿using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using MultiResolutioning.Data;
using MultiResolutioning.Structures;

namespace MultiResolutioning.Files
{
	public static class ProgressiveMeshFile
	{
		/// <summary>
		/// Wczytuje siatkę podstawowa do zwracanego stringa.
		/// </summary>
		/// <param name="path">Sciezka.</param>
		public static string LoadMeshToString(string path)
		{
			StreamReader reader = new StreamReader(path + ".pmo");
			return reader.ReadToEnd();
		}

		/// <summary>
		/// Ustawia strukture ze stringa.
		/// </summary>
		/// <param name="source">Zrodlo.</param>
		public static MeshData LoadMeshFromString(string source)
		{
			StringReader reader = new StringReader(source);
			return ProgressiveMeshFile.LoadMesh(reader);
		}

		/// <summary>
		/// Wczytuje odpowiednie operacje dzielenia z pliku.
		/// </summary>
		/// <param name="path">Sciezka.</param>
		/// <param name="lastSplit">Ostatnie dokonane dzielenie.</param>
		/// <param name="count">Liczba zwracanych operacji.</param>
		/// <returns></returns>
		public static List<Split> LoadSplits(string path, int lastSplit, int count)
		{
			StreamReader reader = new StreamReader(path + ".pms");
			return LoadSplits(reader, lastSplit, count);
		}

		/// <summary>
		/// Wczytuje saitke wielorozdzielcza.
		/// </summary>
		/// <param name="path">Sciezka.</param>
		public static ProgressiveMesh LoadProgressive(string path)
		{
			StreamReader reader = new StreamReader(path + ".pmo");
			MeshData mesh = LoadMesh(reader);

			reader = new StreamReader(path + ".pms");
			List<Split> splitList = LoadSplits(reader);

			return new ProgressiveMesh(mesh, splitList);
		}

		/// <summary>
		/// Zapisuje siatke wielorozdzielcza.
		/// </summary>
		/// <param name="progressive">Siatka wielorozdzielcza.</param>
		/// <param name="path">Sciezka.</param>
		public static void SaveProgressive(ProgressiveMesh progressive, string path)
		{
			CultureInfo ciDotSeparator = (CultureInfo)CultureInfo.CurrentCulture.Clone();
			ciDotSeparator.NumberFormat.NumberDecimalSeparator = ".";

			StreamWriter sw = new StreamWriter(path + ".pmo", false);

			sw.WriteLine("vertices:" + progressive.mesh.vertices.Count);
			sw.WriteLine("faces:" + progressive.mesh.faces.Count);

			int i = 0;
			foreach (Vertex vertex in progressive.mesh.vertices)
			{
				sw.Write(
					vertex.vertexId + " " +
					vertex.position.X.ToString(ciDotSeparator) + " " +
					vertex.position.Y.ToString(ciDotSeparator) + " " +
					vertex.position.Z.ToString(ciDotSeparator) + "\n");

				progressive.mesh.vertices[i].saveId = i;
				++i;
			}

			foreach (Face face in progressive.mesh.faces)
			{
				sw.Write(
					face.faceId + " " +
					face.vertices[0].saveId + " " +
					face.vertices[1].saveId + " " +
					face.vertices[2].saveId + "\n");
			}

			sw.Close();

			sw = new StreamWriter(path + ".pms", false);

			sw.WriteLine("splits:" + progressive.splits.Count);

			foreach (Split split in progressive.splits)
			{
				sw.Write(
					split.vertexIdToSplit + " " +
					split.v1Id + " " +
					split.positionV1.X.ToString(ciDotSeparator) + " " +
					split.positionV1.Y.ToString(ciDotSeparator) + " " +
					split.positionV1.Z.ToString(ciDotSeparator) + " " +

					split.v2Id + " " +
					split.positionV2.X.ToString(ciDotSeparator) + " " +
					split.positionV2.Y.ToString(ciDotSeparator) + " " +
					split.positionV2.Z.ToString(ciDotSeparator) + " " +
					split.faceIdsV1.Count + " ");

				foreach (int faceId in split.faceIdsV1)
				{
					sw.Write(faceId + " ");
				}

				sw.Write(split.removedData.Count + " ");

				foreach (FaceSmall fs in split.removedData)
				{
					sw.Write(
						fs.faceId + " " +
						fs.verticesIds[0] + " " +
						fs.verticesIds[1] + " " +
						fs.verticesIds[2] + " ");
				}

				sw.Write("\n");
			}

			sw.Close();
		}

		private static MeshData LoadMesh(TextReader reader)
		{
			CultureInfo ciDotSeparator = (CultureInfo)CultureInfo.CurrentCulture.Clone();
			ciDotSeparator.NumberFormat.NumberDecimalSeparator = ".";
			string input;
			string[] splited;

			int verticesCount;
			int facesCount;

			MeshData mesh = new MeshData();
			Vertex tempVertex;
			Face tempFace;

			//StreamReader reader = new StreamReader(path + ".pmo");

			// vertices number
			input = reader.ReadLine();
			splited = input.Split(':');

			verticesCount = int.Parse(splited[1]);
			mesh.vertices = new List<Vertex>(verticesCount);

			// faces number
			input = reader.ReadLine();
			splited = input.Split(':');

			facesCount = int.Parse(splited[1]);
			mesh.faces = new List<Face>(facesCount);

			// reading vertices
			for (int i = 0; i < verticesCount; ++i)
			{
				input = reader.ReadLine();
				splited = input.Split(' ');

				tempVertex = new Vertex(
							float.Parse(splited[1], ciDotSeparator),
							float.Parse(splited[2], ciDotSeparator),
							float.Parse(splited[3], ciDotSeparator));

				tempVertex.vertexId = int.Parse(splited[0]);

				mesh.vertices.Add(tempVertex);
			}

			int vertexId;

			for (int i = 0; i < facesCount; ++i)
			{
				input = reader.ReadLine();
				splited = input.Split(' ');
				tempFace = new Face();

				vertexId = int.Parse(splited[1]);
				tempFace.vertices[0] = mesh.vertices[vertexId];
				mesh.vertices[vertexId].faces.Add(tempFace);

				vertexId = int.Parse(splited[2]);
				tempFace.vertices[1] = mesh.vertices[vertexId];
				mesh.vertices[vertexId].faces.Add(tempFace);

				vertexId = int.Parse(splited[3]);
				tempFace.vertices[2] = mesh.vertices[vertexId];
				mesh.vertices[vertexId].faces.Add(tempFace);

				tempFace.vertices[0].neighbors.Add(tempFace.vertices[1]);
				tempFace.vertices[0].neighbors.Add(tempFace.vertices[2]);

				tempFace.vertices[1].neighbors.Add(tempFace.vertices[0]);
				tempFace.vertices[1].neighbors.Add(tempFace.vertices[2]);

				tempFace.vertices[2].neighbors.Add(tempFace.vertices[0]);
				tempFace.vertices[2].neighbors.Add(tempFace.vertices[1]);

				foreach (Vertex v in tempFace.vertices)
				{
					v.neighbors = v.neighbors.Distinct().ToList();
				}

				tempFace.faceId = int.Parse(splited[0]);
				mesh.faces.Add(tempFace);
			}

			reader.Close();
			return mesh;
		}

		/// <summary>
		/// Wczytuje operacje dzielenia do listy.
		/// </summary>
		/// <param name="reader">Kontekst czytajacy.</param>
		/// <param name="lastSplit">Ostatnie dokonane dzielenie.</param>
		/// <param name="count">Liczba zwracanych dzielen.</param>
		/// <returns></returns>
		public static List<Split> LoadSplits(TextReader reader, int lastSplit, int count)
		{
			CultureInfo ciDotSeparator = (CultureInfo)CultureInfo.CurrentCulture.Clone();
			ciDotSeparator.NumberFormat.NumberDecimalSeparator = ".";

			//StreamReader reader = new StreamReader(path + ".pms");

			string input = reader.ReadLine();
			string[] splited = input.Split(':');

			Split tempSplit;

			int end;
			int begin = 0;

			int splitsCount = int.Parse(splited[1]);

			if (splitsCount > count)
			{
				if (lastSplit + count > splitsCount)
				{
					splitsCount = splitsCount - lastSplit;
					//begin = lastSplit + 1;
					//splitsCount = end;
				}
				else
				{
					end = splitsCount - lastSplit;
					begin = end - count;
					splitsCount = count;
				}
			}

			List<Split> splitList = new List<Split>(splitsCount);
			
			for (int i = 0; i < begin; ++i )
			{
				reader.ReadLine();
			}

			for (int i = 0; i < splitsCount; ++i)
			{
				input = reader.ReadLine();
				splited = input.Split(' ');

				Vertex vSplit = new Vertex() { vertexId = int.Parse(splited[0]) };
				Vertex v1 = new Vertex(
					float.Parse(splited[2], ciDotSeparator),
					float.Parse(splited[3], ciDotSeparator),
					float.Parse(splited[4], ciDotSeparator)) { vertexId = int.Parse(splited[1]) };

				Vertex v2 = new Vertex(
					float.Parse(splited[6], ciDotSeparator),
					float.Parse(splited[7], ciDotSeparator),
					float.Parse(splited[8], ciDotSeparator)) { vertexId = int.Parse(splited[5]) };

				int temp = int.Parse(splited[9]);
				int facesIndex = temp + 10;
				List<int> facesV1 = new List<int>(temp);

				for (int j = 10; j < facesIndex; ++j)
				{
					facesV1.Add(int.Parse(splited[j]));
				}

				temp = int.Parse(splited[facesIndex]);
				List<FaceSmall> removedList = new List<FaceSmall>(temp);

				for (int j = 0; j < temp; ++j)
				{
					int tempIndex = facesIndex + j * 4;

					removedList.Add(new FaceSmall()
					{
						faceId = int.Parse(splited[tempIndex + 1]),
						verticesIds = new List<int>() {
							int.Parse(splited[tempIndex + 2]),
							int.Parse(splited[tempIndex + 3]),
							int.Parse(splited[tempIndex + 4])}
					});
				}

				tempSplit = new Split()
				{
					vertexIdToSplit = vSplit.vertexId,
					v1Id = v1.vertexId,
					v2Id = v2.vertexId,
					positionV1 = v1.position,
					positionV2 = v2.position,
					faceIdsV1 = facesV1,
					removedData = removedList
				};

				splitList.Add(tempSplit);
			}

			reader.Close();

			return splitList;
		}

		/// <summary>
		/// Wczytuje wszystkie operacje dzielenia do listy.
		/// </summary>
		/// <param name="reader">The reader.</param>
		/// <returns></returns>
		public static List<Split> LoadSplits(TextReader reader)
		{
			// loading all splits
			return LoadSplits(reader, 0, int.MaxValue);
		}
	}
}
